-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Cross-program invocations #9582
Conversation
Hitting an error loading the solana_bpf_loader_program on linux: Debugging... wip label for now |
Codecov Report
@@ Coverage Diff @@
## master #9582 +/- ##
========================================
- Coverage 80.5% 80.3% -0.3%
========================================
Files 280 281 +1
Lines 64031 64615 +584
========================================
+ Hits 51592 51897 +305
- Misses 12439 12718 +279 |
Fixed and ready for review, there's a lot in there so let the critiques commence! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me. I found the term "cross-program" to be a little confusing. I think it's used to mean at least "foreign function call" and "cross-program process_instruction". Even though I've been calling the function process_instruction()
, it looks like calling it invoke()
would nicely disambiguate it from MessageProcessor::process_instruction()
and allow you to use "invoke" or "invocation" in places you're currently using "cross_program". Maybe even "solana_ffi::invoke".
As for the new runtime rules, I didn't review it in detail, but it all looked consistent with the design and what we've discussed on Discord. And lots of tests, so not a lot to worry about. Hoping we can get this merged soon and into v1.2.0.
@garious I'm a fan of |
@jackcmay, how about |
|
@greg The full path would be comprised of
Did you have something different in mind? |
@jackcmay Got it. I missed that. What about |
Originally had it in Along your lines of thinking we could do: |
I like that a lot |
|
||
// Process instruction | ||
|
||
let program_account = (*accounts[program_id_index]).clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we only need to clone the program data if the program is also an instruction account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are you referring to as an instruction account?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant an account that is passed as an argument to an instruction
const DERIVED_KEY2_INDEX: usize = 7; | ||
|
||
entrypoint!(process_instruction); | ||
fn process_instruction( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be a test for cross program instructions with only a subset of the caller accounts.
Also, is there a min / max number of accounts that can be passed to cross_program::process_instruction
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea will add those tests. Also, there is currently no limit to the number of accounts that can be passed beside the memory restrictions of the calling process. This could be a ddos vulnerability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, is there a reason to do this? The runtime only passes the accounts listed in the instruction to the invoked program. Breaking up the accounts vector isn't a fun thing to do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok maybe I'm missing something. What if a program is called with 2 account arguments and wants to call a program that needs 3?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it fails, programs need to be passed all the accounts it needs to pass to any invokes calls it intends to make
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I think I figured out the disconnect!
I was imagining that the caller program could rearrange, filter, dupe accounts etc. and then pass that new account slice to the called function. But that's overkill because as you mentioned, the cross program instruction just needs to reference the indices of the accounts from the original account slice.
As it stands, the caller could still choose to pass only a subset of the accounts to the called program I think. I don't see a check to make sure that all accounts in pre_accounts are present in the accounts slice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that the caller needs to pass the accounts slice back out through sol_process_signed_instruction_
? Can't we check for them how accounts have been modified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhh we are doing that. Sorry!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, no we aren't actually 😅
Does this make sense? Do we need the caller to pass through all the account infos? Don't we already know where the accounts are memory mapped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This issue is summarized in #9711 and is planned as a followup change to the PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like malicious programs could read the entirety of a memory region if they mess with the serialized lengths. Is that problematic?
@jstarry We can't enforce that the passed buffers are sized correctly or even pointing to anything valid. We can only verify that the pointer and size of the buffer have been granted to the calling program. The called program only has access to what has been passed by the caller, aka the called can't read or write memory of the caller's address space outside of what the caller passed. |
Ok, sounds good! |
@garious Take a look at the new naming, like how it looks now that its in? |
@mvines @jstarry @garious if self.program_ids.contains(key) && self.program_ids.last() != Some(key) {
// Reentrancy not allowed unless caller is calling itself
return Err(InstructionError::ReentrancyNotAllowed);
} Here are also some follow-up issues that came out of the review or todos:
Anything else? |
Love it! |
// Verify the called program has not misbehaved | ||
result = invoke_context.verify_and_update(message, instruction, signers, accounts); | ||
} | ||
invoke_context.pop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jackcmay Sorry for being after-party comment, but I just started to looking the program-related code. :)
This invoke_context.pop()
isn't always be paired with invoke_context.push(...)
when keyed_accounts[0].owner()?
fails to borrow. Is that ok?
And qq: is cross-program invocation can be gracefully handled inside a invoker program in some way when invokee's result is InstructionError
?
Then, this verification might start to reference a wrong program_id
.
I think to it's better to avoid these kind of things like with closure surrounded by push()
and pop()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that will never fail in this implementation so I left it. I recently updated this code to extend cross-program invocation to native programs and in those changes the case you mention no longer exists.
I'm not quite following the "qq", if the invoke fails the result is held and context poped before returning. Can you elaborate?
Problem
Summary of Changes
Implements the following proposals
This PR has been whittled down to the essential changes by breaking up all dependent and related changes into other PRs that have already been committed.
Fixes #